/*
 * Copyright (C) 2010 M. Hautle.
 * 
 * This file is part of SimpleEL.
 * 
 * SimpleEL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * SimpleEL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with SimpleEL. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.simpleel.internal.asm;

import java.beans.*;
import ch.msoftch.internal.*;
import ch.simpleel.*;
import ch.simpleel.ValueAccessorManager.IAccessorProvider;
import ch.simpleel.accessors.*;
import ch.simpleel.internal.*;
import ch.simpleel.internal.reflection.*;
import ch.simpleel.util.*;

/**
 * {@link IAccessorProvider} backed by ASM generated elements.<br>
 * This provider is only designed to work as provider for {@link ValueAccessorManager}, do not use it for other purposes!
 * 
 * @author hautle
 */
public class ASMAccessProvider implements IAccessorProvider {
    /** Cache for property {@link IValueAccessor}s. */
    private final ICache<PropertyKey, IValueAccessor> cache = CacheFactory.createCache(PropertyKey.class, IValueAccessor.class);

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> IValueAccessor<T> getELAccessor(String el, Class<T> returnType) {
        return new ELAccessor<T>(el, returnType);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> IValueAccessor<T> getPropertyAccessor(Class<?> c, String property) {
        final IValueAccessor<T> acc = getOrCreateAccessor(c, property);
        if (acc == null)
            throw new IllegalArgumentException("On " + c.getName() + " exists no property named " + property + "!");
        return acc;
    }

    /**
     * Returns an accessor for the specified property.
     * 
     * @param <T> The return type
     * @param c The bean class
     * @param property The property name
     * @return An corresponding accessor or null if no such property exists
     */
    @SuppressWarnings("unchecked")
    private <T> IValueAccessor<T> getOrCreateAccessor(Class<?> c, String property) {
        final PropertyKey k = new PropertyKey(c, property);
        IValueAccessor<T> acc = cache.get(k);
        if (acc == null) {
            final PropertyDescriptor desc = ReflectionHelper.getDescriptor(c, property);
            if (desc == null)
                return null;
            cache.put(k, acc = new PropertyAccessor<T>(desc, ASMAccessorFactory.getAccessor(c)));
        }
        return acc;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T getValue(Object bean, String property, T fallback) throws ValueAccessException {
        try {
            final IValueAccessor<T> acc = getOrCreateAccessor(bean.getClass(), property);
            if (acc == null)
                return fallback;
            return acc.getValue(bean, fallback);
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            throw new ValueAccessException(property, e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean setValue(Object bean, String property, Object value) throws ValueAccessException {
        try {
            final IValueAccessor<Object> acc = getOrCreateAccessor(bean.getClass(), property);
            if (acc == null)
                return false;
            return acc.setValue(bean, value);
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            throw new ValueAccessException(property, e);
        }
    }

    /**
     * {@link IValueAccessor} for simple properties.
     * 
     * @param <T> The return type
     * @author M. Hautle
     */
    private static class PropertyAccessor<T> implements IValueAccessor<T> {
        /** The property name. */
        private final String property;

        /** Flag indicating if the property has a read method. */
        private final boolean read;

        /** Flag indicating if the property has a write method. */
        private final boolean write;

        /** The return type of the property */
        private final Class<T> returnType;

        /** The accessor for the property. */
        private final IPropertyAccessor accessor;

        /**
         * Default constructor.
         * 
         * @param p The property descriptor
         * @param accessor The accessor for the property
         */
        @SuppressWarnings("unchecked")
        public PropertyAccessor(PropertyDescriptor p, IPropertyAccessor accessor) {
            this.accessor = accessor;
            this.property = p.getName();
            read = p.getReadMethod() != null;
            write = p.getWriteMethod() != null;
            returnType = (Class<T>) p.getPropertyType();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getPath() {
            return property;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        @SuppressWarnings("unchecked")
        public T getValue(Object element, T fallback) {
            if (!read)
                return fallback;
            try {
                return (T) accessor.get(element, property);
            } catch (Exception e) {
                throw new ValueAccessException(property, e);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean setValue(Object element, T value) {
            try {
                if (!write)
                    return false;
                accessor.set(element, property, value);
                return true;
            } catch (Exception e) {
                throw new ValueAccessException(property, e);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Class<T> getType() {
            return returnType;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isReadable() {
            return read;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isWritable() {
            return write;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return getPath() + " (" + getType().getName() + ")";
        }
    }
}
